#include "../inc/main.h"
#include "../inc/transmitFile.h"
#include "../inc/md5.h"
#include "../inc/passwordConfirm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>


int sendCommand(int serverFileDescribe, const char *commandBuffer, int commandType);
int getFirstArgumentPosition(const char *commandBuffer, int *position);
int getOneFrame(int serverFileDescribe, protocol_t *pProtocol);
int putFile(int serverFileDescribe, const char *fileName);
int getFile(int serverFileDescribe,const char* fileName);
int userSignIn(int serverFileDescribe);

int main(int argc,char** argv) {
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    ERROR_CHECK(sfd, -1, "socket");

    struct sockaddr_in serAddr;
    memset(&serAddr, 0, sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));

    int ret = connect(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr));
    ERROR_CHECK(ret, -1, "connect");

    userSignIn(sfd);

    protocol_t *pProtocol = (protocol_t *)malloc(sizeof *pProtocol + MAX_DATA_SIZE);

    char buf[128] = {0};
    fd_set rdset;
    FD_ZERO(&rdset);
    int readyNum = 0;
    while(1) {
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(sfd, &rdset);
        readyNum = select(sfd+1, &rdset, NULL, NULL, NULL);
        printf("readyNum = %d\n", readyNum);
        for (int i = 0; i < readyNum; ++i){
            if (FD_ISSET(STDIN_FILENO, &rdset)){
                memset(buf, 0, sizeof(buf));
                int readLength = read(STDIN_FILENO, buf, sizeof(buf));
                buf[readLength - 1] = 0;
                if(!strncmp("puts",buf,4))    
                {
                    sendCommand(sfd, buf, PUTS);
                    char *fileName = buf;
                    while (' ' != *fileName) fileName++;
                    while (' ' == *fileName) fileName++;
                    putFile(sfd, fileName);
                }
                else if(!strncmp("gets",buf,4))
                {
                    sendCommand(sfd, buf, GETS);
                    char *fileName = buf;
                    while (' ' != *fileName) fileName++;
                    while (' ' == *fileName) fileName++;
                    getFile(sfd, fileName);
                }
                else if(!strncmp("cd",buf,2))
                {
                    sendCommand(sfd, buf, CD);
                }
                else if(!strncmp("ls",buf,2))
                {
                    pProtocol->type = LS;
                    pProtocol->size = 0;
                    send(sfd, pProtocol, getProtocolSize(pProtocol), 0);
                }
                else if(!strncmp("pwd",buf,3))
                {
                    pProtocol->type = PWD;
                    pProtocol->size = 0;
                    send(sfd,pProtocol,getProtocolSize(pProtocol), 0);
                }
                else if(!strncmp("rm",buf,2))
                {
                    sendCommand(sfd, buf, RM);
                }
                else if(!strncmp("mkdir",buf,5))
                {
                    sendCommand(sfd, buf, MKDIR);
                }
            }
            if(FD_ISSET(sfd, &rdset)){
                memset(pProtocol->data, 0, MAX_DATA_SIZE + 1);
                getOneFrame(sfd, pProtocol);
                if(pProtocol->type == MESSAGE)
                {
                    printf("%s\n",pProtocol->data);
                }
            }
        }
    }
    return 0;
}

int userSignIn(int serverFileDescribe) {
    char message[1508] = {0};
    protocol_t *pProtocol = (protocol_t *)message;
    
    while (1) {
        //USER_SIGNIN表示这是用户请求登录的报文，包含用户名和密码
        pProtocol->type = USER_SIGNIN;

        //输入用户名
        printf("请输入用户名：");
        fflush(stdout);
        scanf("%s", pProtocol->data);

        //用户名和密码之间用空格隔开
        pProtocol->data[strlen(pProtocol->data)] = ' ';

        //输入密码，不显示在终端
        char *password = getpass("请输入密码：");
        strcpy(pProtocol->data + strlen(pProtocol->data), password);

        pProtocol->size = strlen(pProtocol->data);

        int ret = send(serverFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
        ERROR_CHECK(ret, -1, "send");

        getOneFrame(serverFileDescribe, pProtocol);
        if (pProtocol->type = PASSWD_RIGHT) {
            printk("密码正确!\n");
            break;
        }
        printk("密码或用户名错误!\n");
    }
    return 0;
}


/*
 * @brief 对recv函数的返回值进行check
 * @param in recvReturnValue recv函数的返回值
 * @return 返回0表示recv正常读取，-1表示读取异常，-2表示断开连接
 */
static inline int recvReturnCheck(int recvReturnValue) {
    if (0 == recvReturnValue) {
        printk("client disconnect!\n");
        return -2;
    }
    if (-1 == recvReturnValue) {
        return -1;
    }
    return 0;
}

/*
 * @brief 获取一帧数据，即一个结构体protocol_t存储的数据
 * @param in clientFileDescribe 客户端文件描述符
 * @param out pProtocol 用于返回获取到的一帧数据
 * @return 返回0表示获取成功，-1表示获取失败，-2表示断开连接
 */
int getOneFrame(int clientFileDescribe, protocol_t *pProtocol) {
    int type = 0;
    int size = 0;
    // 获取数据类型
    int ret = recv(clientFileDescribe, &type, sizeof type, MSG_WAITALL);
    // printk("get command type 0x%4X\n", type);
    if (0 != (ret = recvReturnCheck(ret))) {
        return ret;
    }
    // 获取数据长度
    ret = recv(clientFileDescribe, &size, sizeof size, MSG_WAITALL);
    if (0 != (ret = recvReturnCheck(ret))) {
        return ret;
    }
    pProtocol->type = type;
    pProtocol->size = size;
    if (0 == size) { 
        // printk("command size = 0\n"); 
        return 0; 
    }
    // 获取数据部分
    ret = recv(clientFileDescribe, pProtocol->data, pProtocol->size, MSG_WAITALL);
    if (0 != recvReturnCheck(ret)) {
        return -1;
    }
    // printk("get command: %s\n", pProtocol->data);
    pProtocol->data[size] = 0;
    return 0;
}

int getFirstArgumentPosition(const char *commandBuffer, int *position) {
    *position = 0;
    while (' ' != *commandBuffer) { commandBuffer++; (*position)++; }
    while (' ' == *commandBuffer) { commandBuffer++; (*position)++; }
    return 0;
}

int sendCommand(int serverFileDescribe, const char *commandBuffer, int commandType) {
    protocol_t *pProtocol = (protocol_t *)malloc(sizeof *pProtocol + MAX_DATA_SIZE);
    pProtocol->type = commandType;
    int position = 0;
    getFirstArgumentPosition(commandBuffer, &position);
    pProtocol->size = strlen(commandBuffer + position);
    strcpy(pProtocol->data, commandBuffer + position);
    send(serverFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
    return 0;
}

#define PERM 0664  //权限
/*
 * @brief 循环接收函数
 * @param clientFileDescribe in 对方的文件描述符
 * @param totallen in 要接收的文件总长度
 * @param pData out 用于接收实际信息的buf
 * @return 非0值表示成功接收到文件的大小，-1表示对方离线接收失败
 */
int recvCycle(const int clientFileDescribe, const int totallen, void *pData){
    int ret = 0;  //接收函数返回值
    int recvlen = 0;  //已接收文件的大小
    while (recvlen < totallen) {
        ret = recv(clientFileDescribe, (char *)pData + recvlen, totallen - recvlen, 0);
        recvlen += ret;
        if (0 == ret){
            return ret;
        }
    }
    return recvlen;
}

/*
 * @brief 文件接收函数
 * @param clientFileDescribe in 对方的文件描述符
 * @param fileName in 要接收的文件名
 * @return 0表示接收成功，-1表示接收失败
 */
int getFile(int clientFileDescribe,const char* fileName){
    //工具
    int type = 0;  //传输的数据类型
    int size = 0;  //data的大小
    char data[MAX_DATA_SIZE] = {0};  //实际使用的信息
    int ret = 0;  //接收函数返回值
    int fd = 0;
    off_t offset = 0;
    off_t fileLen = 0;  //接收文件总长度
    off_t recvLen = 0;  //已接收的文件长度
    protocol_t *pProtocol = (protocol_t *)calloc(1, sizeof *pProtocol + MAX_DATA_SIZE + 1);
    //判断是否有同名文件存在，并发送偏移量
    pProtocol->type = FILE_OFFSET;
    pProtocol->size = sizeof offset;
    if (-1 != (fd = open(fileName, O_RDONLY))) {
        struct stat fileStatus;
        fstat(fd, &fileStatus);
        offset = fileStatus.st_size;
        recvLen += offset;
        close(fd);
    }
    memcpy(pProtocol->data, &offset, sizeof offset);
    send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);

    //接收文件名
    // recv(clientFileDescribe,&type,sizeof(type),0);
    // recv(clientFileDescribe,&size,sizeof(size),0);
    // recv(clientFileDescribe,data,size,0);
    //接收文件的md5值
    // getOneFrame(clientFileDescribe, pProtocol);
    //创建同名文件
    printf("fileName %s\n", fileName);
    fd = open(fileName, O_RDWR|O_CREAT, PERM);
    if (-1 == fd) {
        printk("创建文件失败\n");
        goto run_error;
    }

    //接收文件的大小信息
    getOneFrame(clientFileDescribe, pProtocol);
    memcpy(&fileLen, pProtocol->data, sizeof fileLen);
    printk("fileLen = %ld\n", fileLen);

    //接收文件内容
    //小文件不超过100M
    ret = ftruncate(fd,fileLen);
    ERROR_CHECK(ret,-1,"ftruncate");
    if (fileLen <= (1 << 20) * 100){
        lseek(fd, offset, SEEK_SET);
        while (recvLen < fileLen) {
            memset(data, 0, sizeof(data));
            recv(clientFileDescribe, &type, sizeof type, 0);
            recv(clientFileDescribe, &size, sizeof(size),0);
            ret = recvCycle(clientFileDescribe, size, data);
            if (ret < size) {
                ftruncate(fd, recvLen);
                printk("get file %s interupt!\n", fileName);
                break;
            }
            recvLen += ret;
            float rate = (float)recvLen/fileLen * 100;
            printk("rate = %5.2f %%\n", rate);
            fflush(stdout);
            write(fd,data,ret);
        }
    }
    //大文件超过100M
    else{
        char* pMap = (char*)mmap(NULL, fileLen, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        ERROR_CHECK(pMap, (char*)-1,"mmap");
        ret = recvCycle(clientFileDescribe, fileLen - offset, pMap + offset);
        munmap(pMap, fileLen);
        if (ret < fileLen) {
            ftruncate(fd, ret + offset);  // 传输中断时，将文件大小截断成已接收大小
            printk("get file %s interupt!\n", fileName);
        }
    }
    printk("get file %s success!\n", fileName);
    close(fd);
run_error:
    free(pProtocol);
    return 0;
}

#define MD5_SIZE        16
#define MD5_STR_LEN     (MD5_SIZE * 2)

// 发送文件到服务器
int putFile(int clientFileDescribe, const char *fileName){
    protocol_t *pProtocol = (protocol_t *)calloc(1, sizeof *pProtocol + MAX_DATA_SIZE);

    off_t offset = 0;
    memset(pProtocol, 0, sizeof *pProtocol + MAX_DATA_SIZE);
    pProtocol->size = strlen(fileName);
    /* strcpy(pProtocol->data, fileName); */
    getOneFrame(clientFileDescribe, pProtocol);
    if (pProtocol->type == FILE_EXIST) {
        printk("file exist\n");
        goto exit_error;
    }
    // 打开文件
    int fd = open(fileName, O_RDWR);
    if (-1 == fd) {
        printk("传输失败，请检查文件名\n");
        goto exit_error;
    }
    // 发送文件名
    // send(clientFileDescribe, &pProtocol, getProtocolSize(pProtocol), 0);
    // 发送md5
    // if(pProtocol->type == FILE_MD5){
    //     int ret;
    //     /* const char *file_path = "你的filename"; */
    //     char md5_str[MD5_STR_LEN + 1];
    //     memset(md5_str,0,sizeof(md5_str));
    //     ret = Compute_file_md5(fileName, md5_str);
    //     if (0 == ret){
    //         printk("[file - %s] md5 value:", fileName);
    //         printk("%s\n", md5_str);
    //         /* 再给对方发送一份 */
    //         pProtocol->type = FILE_MD5;
    //         pProtocol->size = strlen(md5_str);
    //         strcpy(pProtocol->data, md5_str);
    //         send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
    //     }
    // }
    //获取文件信息
    struct stat fileInfo;
    bzero(&fileInfo, sizeof(fileInfo));

    fstat(fd, &fileInfo);
    pProtocol->type = FILE_SIZE;
    pProtocol->size = sizeof(fileInfo.st_size);
    memcpy(pProtocol->data, &fileInfo.st_size, pProtocol->size);
    //发送文件大小
    if(pProtocol->type == FILE_SIZE){
        send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
        printk("fileInfo = %ld\n", fileInfo.st_size);
    }
    //发送文件内容
    pProtocol->type = FILE_DATA;
    if(fileInfo.st_size <= (1 << 20) * 100){
        lseek(fd, offset, SEEK_SET);
        int sum = 0;
        while(sum < fileInfo.st_size){
            pProtocol->size = read(fd, pProtocol->data, MAX_DATA_SIZE);
            int ret = send(clientFileDescribe, pProtocol,getProtocolSize(pProtocol), 0);
            sum += (pProtocol->size);
            if(-1 == ret){
                break;
            }
            if(0 == pProtocol->size){
                break;
            }
        }
    }
    else{
        char *pMap=(char *)mmap(NULL, fileInfo.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//内存映射，
        ERROR_CHECK(pMap,(char *)-1,"mmap");
        send(clientFileDescribe, pMap, fileInfo.st_size, 0);//发送
        munmap(pMap,fileInfo.st_size);//解除映射
    }
    printk("send file %s success!\n", fileName);
    close(fd);
exit_error:
    free(pProtocol);
    return 0;
}